package media

import (
	"encoding/csv"
	"fmt"
	"github.com/PuerkitoBio/goquery"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"time"
	"ytDownload/entity"
	"ytDownload/logger"
	"ytDownload/tokenBucket"
	util "ytDownload/utils"
	"ytDownload/utils/cmd"
)

type VideoIdStruct struct {
	Name     string `name:"名称"`
	Url      string `name:"url"`
	VideoId  string `name:"videoId"`
	VideoUrl string `name:"videoUrl"`
	AudioUrl string `name:"audioUrl"`
}

func getVideoIdFilePath() string {
	return filepath.Join(entity.GetResourceDir(), "videoId.txt")
}

// 获取所有 Handmade Hero 视频 ID
func storeHandmadeVideoId() {
	// 获取 视频 id
	indexUrl := entity.GetHandmadeIndex()

	fb := util.FakeBrowserStruct{
		Url: indexUrl,
	}

	htmlBytes, err := fb.DoGet()
	if err != nil {
		panic(err)
	}

	htmlStr := string(htmlBytes)

	reader := strings.NewReader(htmlStr)
	document, err := goquery.NewDocumentFromReader(reader)

	if err != nil {
		panic(err)
	}

	entries := document.Find(".cineraIndexEntries")
	if entries.Length() == 0 {
		panic("没有找到任何条目")
	}

	sb := strings.Builder{}
	videoArr := []VideoIdStruct{}

	entries.Each(func(i int, entry *goquery.Selection) {
		aSelections := entry.Find("a")
		if aSelections.Length() == 0 {
			panic("没有找到任何条目")
		}
		aNodes := aSelections.Nodes
		if aNodes == nil {
			panic("没有找到任何条目")
		}

		for idx := range aNodes {
			anode := aNodes[idx]
			name := anode.FirstChild.Data

			findHref := false
			hrefVal := util.FindAttribute(anode, "href")
			if len(hrefVal) > 0 {
				findHref = true
				// logger.Infof("[%s] -- %s", name, href)
				sb.WriteString(name)
				sb.WriteString(",")
				sb.WriteString(hrefVal)
				sb.WriteString("\n")

				v := VideoIdStruct{
					Name: name,
					Url:  hrefVal,
				}
				videoArr = append(videoArr, v)
			}

			if !findHref {
				logger.Errorf("[%s]没有找到href", name)
			}
		}
	})

	err = os.WriteFile(getVideoIdFilePath(), []byte(sb.String()), 0666)
	if err != nil {
		panic(err)
	}
	logger.Infof("写入文件成功")

	startIdx := 600
	endIdx := 602
	if endIdx >= len(videoArr) {
		endIdx = len(videoArr)
	}
	videoArr = videoArr[startIdx:endIdx]

	tb := tokenBucket.Create(3, 1*time.Second)
	wg := sync.WaitGroup{}

	sb = strings.Builder{}
	wg.Add(len(videoArr))

	for i := 0; i < len(videoArr); i++ {

		go func(i int) {

			v := &videoArr[i]

			defer func() {
				if r := recover(); r != nil {
					logger.Errorf("[获取videID]panic:[%s]", v.Name)
				}
				wg.Done()
			}()

			tb.TryTakeOne()

			time.Sleep(2 * time.Second)

			fb := util.FakeBrowserStruct{
				Url: v.Url,
			}

			htmlBytes, err := fb.DoGet()
			if err != nil {
				logger.Errorf("[获取videID]请求指定视频页面报错,[%s],error:%v", v.Name, err)
				return
			}

			htmlStr := string(htmlBytes)
			reader := strings.NewReader(htmlStr)
			document, err := goquery.NewDocumentFromReader(reader)
			if err != nil {
				logger.Errorf("[获取videID]解析html报错,[%s],error:%v", v.Name, err)
				return
			}

			selection := document.Find(".video_container")
			if selection.Length() == 0 {
				logger.Errorf("[获取videID]没有指定元素,[%s]", v.Name)
				return
			}

			nodes := selection.Nodes
			for _, node := range nodes {
				val := util.FindAttribute(node, "data-videoId")
				if len(val) > 0 {
					v.VideoId = val
				}
			}

			if len(v.VideoId) <= 0 {
				logger.Errorf("[获取videID]没有找到videoId,[%s]", v.Name)
				return
			}

			logger.Infof(fmt.Sprintf("%s,%s,%s\n", v.Name, v.Url, v.VideoId))

		}(i)
	}

	wg.Wait()

	logger.Infof("写入csv")
	for _, v := range videoArr {
		sb.WriteString(fmt.Sprintf("%s,%s,%s\n", v.Name, v.Url, v.VideoId))
	}

	err = os.WriteFile(filepath.Join(entity.GetResourceDir(),
		fmt.Sprintf("videoId_%d_%d.txt", startIdx+1, endIdx)),
		[]byte(sb.String()), 0666)
	if err != nil {
		panic(err)
	}

}

// 从 CSV 格式文件中读取需要处理的视频 ID
func readCsvAsVideoStruct(startIdx, endIdx int, splitIdx []int) ([]VideoIdStruct, string, []string) {

	// 读取 csv 文件
	csvFile, err := os.Open(getVideoIdFilePath())
	if err != nil {
		panic(err)
	}

	defer csvFile.Close()

	reader := csv.NewReader(csvFile)
	reader.Comma = ','
	reader.LazyQuotes = true
	reader.TrimLeadingSpace = true

	records, err := reader.ReadAll()
	if err != nil {
		panic(err)
	}

	filename := ""
	arr := []int{}
	if splitIdx != nil && len(splitIdx) > 0 {
		arr = append(arr, splitIdx...)
	}
	if len(arr) > 0 {
		arrStr := []string{}
		recordsT := [][]string{}
		for i := 0; i < len(records); i++ {
			for _, idx := range arr {
				if i == idx {
					recordsT = append(recordsT, records[i])
					arrStr = append(arrStr, fmt.Sprintf("%d", i))
				}
			}
		}
		filename = strings.Join(arrStr, "_")
		records = recordsT
	} else {
		// startIdx := 100
		// endIdx := 102
		if endIdx >= len(records) {
			endIdx = len(records)
		}

		records = records[startIdx:endIdx]
		filename = fmt.Sprintf("%d_%d", startIdx+1, endIdx)
	}

	errName := []string{}

	videoArr := []VideoIdStruct{}
	for _, record := range records {
		// logger.Infof("处理[%s]", record[0])
		v := VideoIdStruct{}
		for i, field := range record {
			switch i {
			case 0:
				v.Name = field
				break
			case 1:
				v.Url = field
				break
			case 2:
				v.VideoId = field
				break
			}
		}
		if v.Url == "" || v.Name == "" || v.VideoId == "" {
			logger.Errorf("[%s]注意,数据不完整,%v", v.Name, v)
			errName = append(errName, v.Name)
		} else {
			videoArr = append(videoArr, v)
		}
	}

	return videoArr, filename, errName
}

func storeDownloadUrlAsEf2() {

	saveDir := entity.GetSaveDir()
	if len(saveDir) <= 0 {
		panic("请先设置保存目录")
	}

	stat, err := os.Stat(saveDir)
	if err != nil || !stat.IsDir() {
		logger.Errorf("保存目录不存在")
		os.MkdirAll(saveDir, os.ModePerm)
	}

	videoArr, filename, errName := readCsvAsVideoStruct(100, 200, nil)

	// 获取完需要处理的 video 了
	downloadUrls := strings.Builder{}

	defer func() {
		if r := recover(); r != nil {
			logger.Errorf("panic:%v", r)
		}
		if downloadUrls.Len() > 0 {
			err := os.WriteFile(filepath.Join(entity.GetResourceDir(),
				fmt.Sprintf("downloadUrl_%s.txt", filename)),
				[]byte(downloadUrls.String()), 0666)
			if err != nil {
				panic(err)
			}
		}

	}()

	for i := range videoArr {
		v := &videoArr[i]
		err := getYtDownloadUrl(v)
		if err != nil {
			logger.Errorf("[%s]注意,获取下载地址报错:%v", v.Name, err)
			errName = append(errName, v.Name)
			continue
		}

		downloadUrls.WriteString(fmt.Sprintf("<\n"+
			"%s\n"+
			"referer: https://www.youtube.com/\n"+
			"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) "+
			"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36\n"+
			"filename: %s_tv.mp4\n"+
			"filepath: %s\n"+
			">\n", v.VideoUrl, v.Name, saveDir))

		downloadUrls.WriteString(fmt.Sprintf("<\n"+
			"%s\n"+
			"referer: https://www.youtube.com/\n"+
			"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) "+
			"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36\n"+
			"filename: %s_ta.m4a\n"+
			"filepath: %s\n"+
			">\n", v.AudioUrl, v.Name, saveDir))

	}

	logger.Infof("\n\n!!!存在失败条目:\n[%s]\n\n", strings.Join(errName, ","))

}

func getYtDownloadUrl(v *VideoIdStruct) (err error) {
	if len(v.VideoId) <= 0 {
		logger.Errorf("[%s]没有videoId", v.Name)
		return
	}

	// 调用 yt-dlp 获取视频音频下载地址
	//  -f "bv[ext=mp4]+ba[ext=m4a]" --get-url "https://www.youtube.com/watch?v=0_xzS8zxuq4"
	logger.Infof("[%s]开始处理[%s]", v.Name, v.VideoId)
	res, err := cmd.ExecuCmd("yt-dlp", "-f", "bv[ext=mp4]+ba[ext=m4a]",
		"--get-url", fmt.Sprintf("https://www.youtube.com/watch?v=%s", v.VideoId))
	if err != nil {
		logger.Errorf("获取下载地址报错:%v", err)
		return
	}

	resStr := res.String()
	logger.Infof("[%s]下载地址:\n%s-----------------------\n", v.Name, resStr)

	re := regexp.MustCompile(`\r?\n`)
	resArr := re.Split(resStr, -1)
	for _, s := range resArr {
		if len(s) > 0 && s[:5] == "https" {
			if len(v.VideoUrl) <= 0 {
				v.VideoUrl = s
			} else {
				v.AudioUrl = s
			}
		}
	}

	if len(v.VideoUrl) <= 0 || len(v.AudioUrl) <= 0 {
		logger.Errorf("[%s]没有找到下载地址:%v", v.Name, v)
		err = fmt.Errorf("[%s]没有找到下载地址:%v", v.Name, v)
		return
	}

	return nil
}
